﻿using System;
using System.Collections.Generic;
using System.Windows.Input;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using MugenMvvmToolkit.Infrastructure;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Interfaces.ViewModels;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.ViewModels;
using MugenMvvmToolkitTest.TestInfrastructure;
using MugenMvvmToolkitTest.TestModels;
using Should;

namespace MugenMvvmToolkitTest.ViewModel
{
    public partial class ViewModelBaseTest
    {
        #region Nested types

        public class ViewModelBaseWithCommand : ViewModelBase
        {
            public ICommand NullCommand { get; set; }

            public ICommand CommandWithoutGetter { private get; set; }

            public ICommand Command { get; set; }
        }

        public class OrderViewModelBase : ViewModelBase
        {
            #region Fields

            public readonly List<string> MethodCallCollection = new List<string>();

            #endregion

            #region Overrides of ViewModelBase

            /// <summary>
            ///     Occurs after the view model is fully loaded.
            /// </summary>
            internal override void OnInitializedInternal()
            {
                MethodCallCollection.Add(InitViewModelInternalKey);
                base.OnInitializedInternal();
            }

            /// <summary>
            ///     Occurs during the initialization of the view model.
            /// </summary>
            protected override void OnInitializing(IInitializationContext context)
            {
                MethodCallCollection.Add(InititalizingViewModelKey);
                base.OnInitializing(context);
            }

            /// <summary>
            ///     Occurs after the view model is fully loaded.
            /// </summary>
            protected override void OnInitialized()
            {
                MethodCallCollection.Add(InitViewModelKey);
                Disposed += OnDisposed;
                base.OnInitialized();
            }

            private void OnDisposed(IViewModel viewModel)
            {
                MethodCallCollection.Add(OnDisposedKey);
            }

            /// <summary>
            ///     Occurs after the current view model is disposed, use for clear resource and event listeners(Internal only).
            /// </summary>
            internal override void OnDisposeInternal(bool disposing)
            {
                MethodCallCollection.Add(DisposeInternalKey);
                base.OnDisposeInternal(disposing);
            }

            /// <summary>
            ///     Occurs after the current view model is disposed, use for clear resource and event listeners.
            /// </summary>
            protected override void OnDispose(bool disposing)
            {
                MethodCallCollection.Add(DisposeKey);
                base.OnDispose(disposing);
            }

            #endregion
        }

        #endregion

        #region Fields

        public const string InititalizingViewModelKey = "InititalizingViewModelKey";
        public const string InitViewModelInternalKey = "InitViewModelInternalKey";
        public const string InitViewModelKey = "InitViewModelKey";
        public const string DisposeInternalKey = "DisposeInternalKey";
        public const string DisposeKey = "DisposeKey";
        public const string OnDisposedKey = "OnDisposedKey";

        #endregion

        #region Test methods

        [TestMethod]
        public void WhenInitialazingVmShouldBindIocAdapterIfSettingIsTrue()
        {
            ViewModelProvider.BindIocAdapter = true;
            ViewModelBase viewModel = GetViewModelBase();
            Type typeFrom = null;
            object item = null;
            string name = null;
            IocAdapter.BindToConstantFunc = (type, arg2, arg3) =>
            {
                typeFrom = type;
                item = arg2;
                name = arg3;
            };
            var testViewModelBase = viewModel.GetViewModel<TestViewModelBase>();

            typeFrom.ShouldEqual(typeof(IIocAdapter));
            item.ShouldEqual(testViewModelBase.IocAdapter);
            name.ShouldBeNull();
        }

        [TestMethod]
        public void WhenInitialazingVmShouldNotBindIocAdapterIfSettingIsFalse()
        {
            ViewModelProvider.BindIocAdapter = false;
            ViewModelBase viewModel = GetViewModelBase();
            bool isInvoked = false;
            IocAdapter.BindToConstantFunc = (type, arg2, arg3) => isInvoked = true;

            viewModel.GetViewModel<TestViewModelBase>();
            isInvoked.ShouldBeFalse();
        }

        [TestMethod]
        public void WhenInitialazingVmShouldUseListenModeFromActivationPolicyIfItIsSetNone()
        {
            ViewModelBase viewModel = GetViewModelBase();
            const ListenType listenType = ListenType.None;

            var testViewModelBase =
                viewModel.GetViewModel<TestViewModelBase>(ActivationPolicy.WithViewUseParentIocAdaper.WithListenMode(listenType));
            viewModel.Observers.Contains(testViewModelBase).ShouldBeFalse();
            testViewModelBase.Observers.Contains(viewModel).ShouldBeFalse();
        }

        [TestMethod]
        public void WhenInitialazingVmShouldUseListenModeFromActivationPolicyIfItIsSetChild()
        {
            ViewModelBase viewModel = GetViewModelBase();
            const ListenType listenType = ListenType.Child;

            var testViewModelBase =
                viewModel.GetViewModel<TestViewModelBase>(ActivationPolicy.WithViewUseParentIocAdaper.WithListenMode(listenType));
            viewModel.Observers.Contains(testViewModelBase).ShouldBeFalse();
            testViewModelBase.Observers.Contains(viewModel).ShouldBeTrue();
        }

        [TestMethod]
        public void WhenInitialazingVmShouldUseListenModeFromActivationPolicyIfItIsSetParent()
        {
            ViewModelBase viewModel = GetViewModelBase();
            const ListenType listenType = ListenType.Parent;

            var testViewModelBase =
                viewModel.GetViewModel<TestViewModelBase>(ActivationPolicy.WithViewUseParentIocAdaper.WithListenMode(listenType));
            viewModel.Observers.Contains(testViewModelBase).ShouldBeTrue();
            testViewModelBase.Observers.Contains(viewModel).ShouldBeFalse();
        }

        [TestMethod]
        public void WhenInitialazingVmShouldUseListenModeFromActivationPolicyIfItIsSetParentAndChild()
        {
            ViewModelBase viewModel = GetViewModelBase();
            const ListenType listenType = ListenType.ParentAndChild;

            var testViewModelBase =
                viewModel.GetViewModel<TestViewModelBase>(ActivationPolicy.WithViewUseParentIocAdaper.WithListenMode(listenType));
            viewModel.Observers.Contains(testViewModelBase).ShouldBeTrue();
            testViewModelBase.Observers.Contains(viewModel).ShouldBeTrue();
        }

        [TestMethod]
        public void WhenInitialazingVmShouldUseListenModeFromGlobalSettingsIfItIsNotSpecifiedExplicitly()
        {
            ViewModelBase viewModel = GetViewModelBase();
            GlobalSettings.ListenViewModelMode = ListenType.ParentAndChild;

            var testViewModelBase = viewModel.GetViewModel<TestViewModelBase>(GlobalSettings.DefaultActivationPolicy);
            viewModel.Observers.Contains(testViewModelBase).ShouldBeTrue();
            testViewModelBase.Observers.Contains(viewModel).ShouldBeTrue();
        }

        [TestMethod]
        public void DefaultBusyMessageShouldEqualsToSettingsDefaultBusyMessage()
        {
            const string busyMessage = "busy...";
            Settings.WithoutClone = true;
            Settings.DefaultBusyMessage = busyMessage;
            ViewModelBase viewModel = GetViewModelBase();

            viewModel.BeginBusy();
            viewModel.BusyMessage.ShouldEqual(busyMessage);
        }

        [TestMethod]
        public void SeveralBusyMessagesShouldBeDisplayedInOrderOfRegistration()
        {
            const string firstBusyMessage = "busy1...";
            const string secondBusyMessage = "busy2...";

            ViewModelBase viewModel = GetViewModelBase();
            var firstId = viewModel.BeginBusy(firstBusyMessage);
            var secondId = viewModel.BeginBusy(secondBusyMessage);

            viewModel.IsBusy.ShouldBeTrue();
            viewModel.BusyMessage.ShouldEqual(secondBusyMessage);
            viewModel.EndBusy(secondId);

            viewModel.IsBusy.ShouldBeTrue();
            viewModel.BusyMessage.ShouldEqual(firstBusyMessage);
            viewModel.EndBusy(firstId);

            viewModel.IsBusy.ShouldBeFalse();
            viewModel.BusyMessage.ShouldBeNull();
        }

        [TestMethod]
        public void HandleBusyMessageShouldNotUpdateMessageIfModeNone()
        {
            const string busyMessageString = "busy...";
            var viewModel = GetViewModelBase();
            viewModel.Settings.HandleBusyMessageMode = HandleType.None;
            var busyMessage = new BeginBusyMessage(Guid.NewGuid(), busyMessageString);
            IHandler<BeginBusyMessage> beginBusyHandler = viewModel;

            beginBusyHandler.Handle(this, busyMessage);
            viewModel.IsBusy.ShouldBeFalse();
            viewModel.BusyMessage.ShouldBeNull();
        }

        [TestMethod]
        public void HandleBusyMessageShouldUpdateMessageIfModeHandle()
        {
            const string busyMessageString = "busy...";
            var viewModel = GetViewModelBase();
            viewModel.Settings.HandleBusyMessageMode = HandleType.Handle;
            var busyMessage = new BeginBusyMessage(Guid.NewGuid(), busyMessageString);
            IHandler<BeginBusyMessage> beginBusyHandler = viewModel;
            IHandler<EndBusyMessage> endBusyHandler = viewModel;

            beginBusyHandler.Handle(this, busyMessage);
            viewModel.IsBusy.ShouldBeTrue();
            viewModel.BusyMessage.ShouldEqual(busyMessageString);

            endBusyHandler.Handle(this, busyMessage.ToEndBusyMessage());
            viewModel.IsBusy.ShouldBeFalse();
            viewModel.BusyMessage.ShouldBeNull();
        }

        [TestMethod]
        public void HandleBusyMessageShouldUpdateMessageAndNotifyListinersIfModeHandleAndNotifyListeners()
        {
            const string busyMessageString = "busy...";
            var viewModel = GetViewModelBase();
            var childViewModel = viewModel.GetViewModel<TestViewModelBase>();
            viewModel.Subscribe(childViewModel);
            viewModel.Settings.HandleBusyMessageMode = HandleType.HandleAndNotifyListeners;
            var busyMessage = new BeginBusyMessage(Guid.NewGuid(), busyMessageString);
            IHandler<BeginBusyMessage> beginBusyHandler = viewModel;
            IHandler<EndBusyMessage> endBusyHandler = viewModel;

            beginBusyHandler.Handle(this, busyMessage);
            viewModel.IsBusy.ShouldBeTrue();
            viewModel.BusyMessage.ShouldEqual(busyMessageString);
            childViewModel.IsBusy.ShouldBeTrue();
            childViewModel.BusyMessage.ShouldEqual(busyMessageString);

            endBusyHandler.Handle(this, busyMessage.ToEndBusyMessage());
            viewModel.IsBusy.ShouldBeFalse();
            viewModel.BusyMessage.ShouldBeNull();
            childViewModel.IsBusy.ShouldBeFalse();
            childViewModel.BusyMessage.ShouldBeNull();
        }

        [TestMethod]
        public void BeginBusyShouldNotifyListiners()
        {
            const string busyMessageString = "busy...";
            var viewModel = GetViewModelBase();
            var childViewModel = viewModel.GetViewModel<TestViewModelBase>();
            viewModel.Subscribe(childViewModel);
            viewModel.Settings.HandleBusyMessageMode = HandleType.HandleAndNotifyListeners;

            var beginBusy = viewModel.BeginBusy(busyMessageString);
            viewModel.IsBusy.ShouldBeTrue();
            viewModel.BusyMessage.ShouldEqual(busyMessageString);
            childViewModel.IsBusy.ShouldBeTrue();
            childViewModel.BusyMessage.ShouldEqual(busyMessageString);

            viewModel.EndBusy(beginBusy);
            viewModel.IsBusy.ShouldBeFalse();
            viewModel.BusyMessage.ShouldBeNull();
            childViewModel.IsBusy.ShouldBeFalse();
            childViewModel.BusyMessage.ShouldBeNull();
        }

        [TestMethod]
        public void ClearBusyShouldRemoveAllBusyMessages()
        {
            const string firstBusyMessage = "busy1...";
            const string secondBusyMessage = "busy2...";

            ViewModelBase viewModel = GetViewModelBase();
            viewModel.BeginBusy(firstBusyMessage);
            viewModel.BeginBusy(secondBusyMessage);

            viewModel.IsBusy.ShouldBeTrue();
            viewModel.BusyMessage.ShouldEqual(secondBusyMessage);
            viewModel.ClearBusy();

            viewModel.IsBusy.ShouldBeFalse();
            viewModel.BusyMessage.ShouldBeNull();
        }

        [TestMethod]
        public void ClearBusyShouldNotifyListiners()
        {
            const string firstBusyMessage = "busy1...";
            const string secondBusyMessage = "busy2...";

            ViewModelBase viewModel = GetViewModelBase();
            var childViewModel = viewModel.GetViewModel<TestViewModelBase>();
            viewModel.Subscribe(childViewModel);
            viewModel.Settings.HandleBusyMessageMode = HandleType.HandleAndNotifyListeners;

            viewModel.BeginBusy(firstBusyMessage);
            viewModel.BeginBusy(secondBusyMessage);

            viewModel.IsBusy.ShouldBeTrue();
            viewModel.BusyMessage.ShouldEqual(secondBusyMessage);
            childViewModel.BusyMessage.ShouldEqual(secondBusyMessage);
            childViewModel.IsBusy.ShouldBeTrue();
            viewModel.ClearBusy();

            viewModel.IsBusy.ShouldBeFalse();
            viewModel.BusyMessage.ShouldBeNull();
            childViewModel.IsBusy.ShouldBeFalse();
            childViewModel.BusyMessage.ShouldBeNull();
        }

        [TestMethod]
        public void WhenVmAddedToListenersItShouldGetAllBusyMessage()
        {
            const string firstBusyMessage = "busy1...";
            const string secondBusyMessage = "busy2...";

            ViewModelBase viewModel = GetViewModelBase();
            viewModel.BeginBusy(firstBusyMessage);
            viewModel.BeginBusy(secondBusyMessage);

            viewModel.IsBusy.ShouldBeTrue();
            viewModel.BusyMessage.ShouldEqual(secondBusyMessage);

            var testViewModelBase = viewModel.GetViewModel<TestViewModelBase>();
            viewModel.Subscribe(testViewModelBase);

            testViewModelBase.IsBusy.ShouldBeTrue();
            testViewModelBase.BusyMessage.ShouldEqual(secondBusyMessage);
        }

        [TestMethod]
        public void WhenVmRemovedFromListenersItShouldRemoveAllBusyMessageFromVm()
        {
            const string firstBusyMessage = "busy1...";
            const string secondBusyMessage = "busy2...";

            ViewModelBase viewModel = GetViewModelBase();
            var childViewModel = viewModel.GetViewModel<TestViewModelBase>(ListenType.None);
            viewModel.Subscribe(childViewModel);

            viewModel.Settings.HandleBusyMessageMode = HandleType.HandleAndNotifyListeners;

            viewModel.BeginBusy(firstBusyMessage);
            viewModel.BeginBusy(secondBusyMessage);

            viewModel.IsBusy.ShouldBeTrue();
            viewModel.BusyMessage.ShouldEqual(secondBusyMessage);
            childViewModel.BusyMessage.ShouldEqual(secondBusyMessage);
            childViewModel.IsBusy.ShouldBeTrue();

            viewModel.Unsubscribe(childViewModel);
            viewModel.IsBusy.ShouldBeTrue();
            viewModel.BusyMessage.ShouldEqual(secondBusyMessage);
            childViewModel.IsBusy.ShouldBeFalse();
            childViewModel.BusyMessage.ShouldBeNull();
        }

        [TestMethod]
        public void GetMethodShouldThrowExceptionIfRequestTypeIsViewModel()
        {
            ViewModelBase viewModel = GetViewModelBase();
            ShouldThrow<ArgumentException>(() => viewModel.Get<TestViewModelBase>());
        }

        [TestMethod]
        public void GetMethodShouldRedirectRequestToIocAdapter()
        {
            const string item = "item";
            const string bindingName = "bindingName";
            var requestParameters = new IIocParameter[1];

            ViewModelBase viewModel = GetViewModelBase();
            Type requestType = null;
            string requestString = null;
            IIocParameter[] iocParameters = null;
            IocAdapter.GetFunc = (type, s, arg3) =>
            {
                requestType = type;
                requestString = s;
                iocParameters = arg3;
                return item;
            };

            viewModel.Get(typeof(string), null, null).ShouldEqual(item);
            requestType.ShouldEqual(typeof(string));
            requestString.ShouldBeNull();
            iocParameters.ShouldBeNull();

            requestType = null;
            requestString = null;
            iocParameters = null;
            viewModel.Get(typeof(string), bindingName, requestParameters).ShouldEqual(item);
            requestType.ShouldEqual(typeof(string));
            requestString.ShouldEqual(bindingName);
            iocParameters.ShouldEqual(requestParameters);

            requestType = null;
            requestString = null;
            iocParameters = null;
            viewModel.Get<string>(requestParameters).ShouldEqual(item);
            requestType.ShouldEqual(typeof(string));
            requestString.ShouldBeNull();
            iocParameters.ShouldEqual(requestParameters);

            requestType = null;
            requestString = null;
            iocParameters = null;
            viewModel.Get<string>(bindingName, requestParameters).ShouldEqual(item);
            requestType.ShouldEqual(typeof(string));
            requestString.ShouldEqual(bindingName);
            iocParameters.ShouldEqual(requestParameters);
        }

        [TestMethod]
        public void VmShouldDisposeIocAdapterIfSettingsTrue()
        {
            ViewModelBase viewModel = GetViewModelBase();
            viewModel.Settings.DisposeIocAdapter = true;
            viewModel.Dispose();
            ((TestIocAdapter)viewModel.IocAdapter).IsDisposed.ShouldBeTrue();
        }

        [TestMethod]
        public void VmShouldNotDisposeIocAdapterIfSettingsFalse()
        {
            ViewModelBase viewModel = GetViewModelBase();
            viewModel.Settings.DisposeIocAdapter = false;
            viewModel.Dispose();
            ((TestIocAdapter)viewModel.IocAdapter).IsDisposed.ShouldBeFalse();
        }

        [TestMethod]
        public void VmShouldDisposeInnerCommandIfSettingsTrue()
        {
            ViewModelBase viewModel = GetViewModelBase();
            var viewModelBaseWithCommand = viewModel.GetViewModel<ViewModelBaseWithCommand>();
            viewModelBaseWithCommand.Settings.DisposeCommands = true;
            var withoutGetter = new TestRelayCommand();
            var command = new TestRelayCommand();

            viewModelBaseWithCommand.NullCommand = null;
            viewModelBaseWithCommand.Command = command;
            viewModelBaseWithCommand.CommandWithoutGetter = withoutGetter;
            viewModelBaseWithCommand.Dispose();
            command.IsDisposed.ShouldBeTrue();
            withoutGetter.IsDisposed.ShouldBeTrue();
        }

        [TestMethod]
        public void VmShouldNotDisposeInnerCommandIfSettingsFalse()
        {
            ViewModelBase viewModel = GetViewModelBase();
            var viewModelBaseWithCommand = viewModel.GetViewModel<ViewModelBaseWithCommand>();
            viewModelBaseWithCommand.Settings.DisposeCommands = false;
            var withoutGetter = new TestRelayCommand();
            var command = new TestRelayCommand();

            viewModelBaseWithCommand.NullCommand = null;
            viewModelBaseWithCommand.Command = command;
            viewModelBaseWithCommand.CommandWithoutGetter = withoutGetter;
            viewModelBaseWithCommand.Dispose();
            command.IsDisposed.ShouldBeFalse();
            withoutGetter.IsDisposed.ShouldBeFalse();
        }

        [TestMethod]
        public void VmShouldClearListenersWhenItDisposed()
        {
            ViewModelBase viewModel = GetViewModelBase();
            var spyHandler = new SpyHandler();
            viewModel.Subscribe(spyHandler);
            viewModel.Observers.Contains(spyHandler).ShouldBeTrue();
            viewModel.Dispose();
            viewModel.Observers.Contains(spyHandler).ShouldBeFalse();
        }

        [TestMethod]
        public void VmShouldClearViewWhenItDisposedIfViewWasInitialized()
        {
            IViewModel vm = null;
            ViewManager.InitializeViewDelegate = context => true;
            ViewManager.CleanupViewOnlyVmDelegate = model => { vm = model; };
            ViewModelBase viewModel = GetViewModelBase();
            viewModel.Dispose();
            vm.ShouldEqual(viewModel);
        }

        [TestMethod]
        public void VmShouldNotClearViewWhenItDisposedIfViewWasNotInitialized()
        {
            IViewModel vm = null;
            ViewManager.InitializeViewDelegate = context => false;
            ViewManager.CleanupViewOnlyVmDelegate = model => { vm = model; };
            ViewModelBase viewModel = GetViewModelBase();
            viewModel.Dispose();
            vm.ShouldBeNull();
        }

        [TestMethod]
        public void VmShouldThrowsExceptionIfItDisposed()
        {
            ViewModelBase viewModel = GetViewModelBase();
            viewModel.Dispose();

            ShouldThrow<ObjectDisposedException>(() => viewModel.BeginBusy());
            ShouldThrow<ObjectDisposedException>(() => viewModel.GetViewModel<TestViewModelBase>());
            ShouldThrow<ObjectDisposedException>(() => viewModel.GetViewModel(typeof(TestViewModelBase)));
            ShouldThrow<ObjectDisposedException>(() => viewModel.GetViewModel(adapter => new TestViewModelBase()));
            ShouldThrow<ObjectDisposedException>(() => viewModel.Get<string>());
            ShouldThrow<ObjectDisposedException>(viewModel.ClearBusy);
            ShouldThrow<ObjectDisposedException>(() => viewModel.InvokeWithBusyIndicator(() => { }));
        }

        [TestMethod]
        public void VmShouldCallMethodsInSpecificOrder()
        {
            ViewModelBase viewModel = GetViewModelBase();
            var orderViewModelBase = viewModel.GetViewModel<OrderViewModelBase>();
            orderViewModelBase.Dispose();

            orderViewModelBase.MethodCallCollection.Count.ShouldEqual(6);
            orderViewModelBase.MethodCallCollection[0].ShouldEqual(InititalizingViewModelKey);
            orderViewModelBase.MethodCallCollection[1].ShouldEqual(InitViewModelInternalKey);
            orderViewModelBase.MethodCallCollection[2].ShouldEqual(InitViewModelKey);
            orderViewModelBase.MethodCallCollection[3].ShouldEqual(DisposeInternalKey);
            orderViewModelBase.MethodCallCollection[4].ShouldEqual(DisposeKey);
            orderViewModelBase.MethodCallCollection[5].ShouldEqual(OnDisposedKey);
        }

        #endregion
    }
}